package fr.cephb.dbsnptools;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

import fr.cephb.util.Cast;


public abstract class SnpHandler
	{
	private SAXParser parser;
	private SaxHandler handler;
	
	public abstract void found(SnpRecord rec);
	
	
	/**
	 * Read a DBSNP XML file
	 */
	private class SaxHandler
	extends DefaultHandler
		{
		private StringBuilder builder=null;
		private SnpRecord record=null;
		private boolean inSequence=false;
		private String assemblyGroupLabel;
		private String assemblyGenomeBuild=null;
		private String componentChromosome=null;
		private String mapLocOrient=null;
		private String mapLocPhysMapInt=null;
		private String leftContigNeighborPos=null;
		private String rightContigNeighborPos=null;
		private List<SnpRecord.FxnSet> fxns= new ArrayList<SnpRecord.FxnSet>();
		public SaxHandler()
			{		
			clear();
			}
		
		private void clear()
			{
			record=null;
			inSequence=false;
			builder=null;
			}
		private void verify(boolean b)
			{
			if(!b) throw new AssertionError();
			}
		@Override
		public void startDocument() throws SAXException
			{
			clear();
			}
		
		@Override
		public void startElement(
				String uri, String localName, String qName,
				Attributes attributes) throws SAXException
			{
			if(localName.equals("Rs"))
				{
				verify(this.record==null);
				this.record=new SnpRecord();
				this.record.setRsId(Integer.parseInt(attributes.getValue("rsId")));
				verify(this.record.getRsId()>0);
				}
			else if(localName.equals("Ss"))
				{
				verify(this.record!=null);
				String s= attributes.getValue("handle");
				if(s!=null) this.record.getHandles().add(s);
				}
			else if(localName.equals("Seq5")||
					localName.equals("Observed") ||
					localName.equals("Seq3"))
				{
				verify(this.record!=null);
				verify(this.record.getRsId()>0);
				this.builder= new StringBuilder();
				}
			else if(localName.equals("Sequence") &&
					attributes.getValue("exemplarSs")!=null)
				{
				verify(this.record!=null);
				verify(this.record.getRsId()>0);
				verify(this.record.getSeq5()==null);
				inSequence=true;
				}
			else if(localName.equals("Assembly"))
				{
				this.assemblyGroupLabel=attributes.getValue("groupLabel");
				if(this.assemblyGroupLabel==null) this.assemblyGroupLabel="N/A";
				this.assemblyGenomeBuild=attributes.getValue("genomeBuild");
				if(this.assemblyGenomeBuild==null) this.assemblyGenomeBuild="N/A";
				}
			else if(localName.equals("Component"))
				{
				this.componentChromosome= attributes.getValue("chromosome");
				if(this.componentChromosome==null) this.componentChromosome="N/A";
				}
			
			else if(localName.equals("MapLoc"))
				{
				mapLocOrient= attributes.getValue("orient");
				if(mapLocOrient==null)
					{
					mapLocOrient="N/A";
					}
				else if(mapLocOrient.equals("forward"))
					{
					mapLocOrient="+";
					}
				else if(mapLocOrient.equals("reverse"))
					{
					mapLocOrient="-";
					}
				mapLocPhysMapInt= attributes.getValue("physMapInt");
				
				
				leftContigNeighborPos= attributes.getValue("leftContigNeighborPos");
				//if(leftContigNeighborPos==null) leftContigNeighborPos="N/A";
				rightContigNeighborPos= attributes.getValue("rightContigNeighborPos");
				//if(rightContigNeighborPos==null) rightContigNeighborPos="N/A";
				}
			else if(localName.equals("FxnSet"))
				{
				SnpRecord.FxnSet fxn=new SnpRecord.FxnSet();
				fxn.fxnClass	=attributes.getValue("fxnClass");
				fxn.geneId	=attributes.getValue("geneId");
				fxn.mrnaAcc	=attributes.getValue("mrnaAcc");
				fxn.mrnaVer	=attributes.getValue("mrnaVer");
				fxn.symbol	=attributes.getValue("symbol");
				fxn.aaPosition	=attributes.getValue("aaPosition");
				fxn.allele	=attributes.getValue("allele");
				fxn.protAcc	=attributes.getValue("protAcc");
				fxn.protVer	=attributes.getValue("protVer");
				fxn.residue	=attributes.getValue("residue");
				fxn.readingFrame=attributes.getValue("readingFrame");
				this.fxns.add(fxn);
				}
			}
		@Override
		public void endElement(String uri, String localName, String qName) 
				throws SAXException
			{
			if(localName.equals("Rs"))
				{
				verify(record!=null);
				verify(record.getRsId()>0);
				verify(record.getSeq5()!=null);
				verify(record.getSeq3()!=null);
				verify(record.getObserved()!=null);
				SnpHandler.this.found(this.record);
				clear();
				}
			else if(inSequence && localName.equals("Seq5") )
				{
				verify(record.getSeq5()==null);
				record.setSeq5(this.builder.toString());
				}
			else if(inSequence && localName.equals("Seq3"))
				{
				verify(record.getSeq3()==null);
				record.setSeq3(this.builder.toString());
				}
			else if(inSequence && localName.equals("Observed"))
				{
				verify(record.getObserved()==null);
				record.setObserved(this.builder.toString());
				}
			else if(localName.equals("Sequence"))
				{
				inSequence=false;
				}
			else if(localName.equals("Assembly"))
				{
				this.assemblyGroupLabel=null;
				this.assemblyGenomeBuild=null;
				}
			else if(localName.equals("Component"))
				{
				this.componentChromosome=null;
				}
			else if(localName.equals("MapLoc"))
				{
				SnpRecord.MapLoc mapLoc=new SnpRecord.MapLoc();
				mapLoc.chromosome= componentChromosome;
				mapLoc.genomeBuild= assemblyGenomeBuild;
				mapLoc.groupLabel= assemblyGroupLabel;
				mapLoc.physMapInt= Cast.Integer.cast(mapLocPhysMapInt);
				mapLoc.orient= mapLocOrient;
				mapLoc.leftContigNeighborPos=Cast.Integer.cast(leftContigNeighborPos);
				mapLoc.rightContigNeighborPos=Cast.Integer.cast(rightContigNeighborPos);
				mapLoc.fxn.addAll(this.fxns);
				this.record.getLocations().add(mapLoc);
				this.fxns.clear();
				mapLocOrient= null;
				mapLocPhysMapInt= null;
				leftContigNeighborPos=null;
				rightContigNeighborPos=null;
				}
			this.builder=null;
			}
		
		@Override
		public void characters(char[] ch, int start, int length)
				throws SAXException
			{
			if(this.builder!=null) this.builder.append(ch,start,length);
			}
		
	
		}
	
	public SnpHandler()
		{
		SAXParserFactory factory= SAXParserFactory.newInstance();
		factory.setNamespaceAware(true);
		try
			{
			this.parser=factory.newSAXParser();
			}
		catch (Exception e)
			{
			throw new RuntimeException(e);
			}
		this.handler=new SaxHandler();
		}
	
	public void parse(InputSource in) throws SAXException,IOException
		{
		this.handler.clear();
		this.parser.parse(in, this.handler);
		}
	}
